package org.xx.armory.commons;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.temporal.TemporalQueries;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import static java.util.Arrays.stream;
import static java.util.Collections.unmodifiableMap;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang3.StringUtils.split;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.xx.armory.commons.Converter.toIntegerEnum;
import static org.xx.armory.commons.Converter.toStringEnum;
import static org.xx.armory.commons.StringConverter.parseDate;
import static org.xx.armory.commons.StringConverter.parseInteger;
import static org.xx.armory.commons.StringConverter.parseLocalDate;
import static org.xx.armory.commons.StringConverter.parseLong;
import static org.xx.armory.commons.Validators.rejectIfBlank;
import static org.xx.armory.commons.Validators.rejectIfNull;

/**
 * Map相关的工具方法。
 */
public final class MapUtils {
    private MapUtils() {
        throw new AssertionError();
    }

    /**
     * 获取指定键的字符串形式的值。
     *
     * @param map
     *         包含需要获取的参数的字典。
     * @param name
     *         参数的名字，会自动去掉首尾空格。
     * @return 参数的字符串形式的值，会自动去掉首尾空格。
     * @throws NullPointerException
     *         如果参数 {@code map} 是 {@code null} 。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public static String getString(
            Map<String, ?> map,
            String name
    ) {
        rejectIfNull(map, "map");
        name = rejectIfBlank(name, "key").trim();

        final var value = map.get(name);
        if (value != null) {
            return trimToEmpty(value.toString());
        } else {
            return "";
        }
    }

    /**
     * 获取指定键的字符串形式的值，并移除该参数。
     *
     * @param map
     *         包含需要获取的参数的字典。
     * @param name
     *         参数的名字，会自动去掉首尾空格。
     * @return 参数的字符串形式的值，会自动去掉首尾空格。
     * @throws NullPointerException
     *         如果参数 {@code map} 是 {@code null} 。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public static String takeString(
            Map<String, ?> map,
            String name
    ) {
        rejectIfNull(map, "map");
        name = rejectIfBlank(name, "key").trim();

        final var value = map.remove(name);
        if (value != null) {
            return trimToEmpty(value.toString());
        } else {
            return "";
        }
    }

    /**
     * 获取指定键的字符串形式的值。
     *
     * @param map
     *         包含需要获取的值的字典。
     * @param name
     *         参数的名字，会自动去掉首尾空格。
     * @return 参数的字符串形式的值，<strong>保留首尾空格</strong>。
     * @throws NullPointerException
     *         如果参数 {@code map} 是 {@code null} 。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public static String getText(
            Map<String, ?> map,
            String name
    ) {
        rejectIfNull(map, "map");
        name = rejectIfBlank(name, "key").trim();

        final var value = map.get(name);
        if (value != null) {
            return value.toString();
        } else {
            return "";
        }
    }

    /**
     * 获取指定键的字符串形式的值。
     *
     * @param map
     *         包含需要获取的参数的字典。
     * @param name
     *         参数的名字，会自动去掉首尾空格。
     * @return 参数的字符串形式的值，<strong>保留首尾空格</strong>。
     * @throws NullPointerException
     *         如果参数 {@code map} 是 {@code null} 。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public static String takeText(
            Map<String, ?> map,
            String name
    ) {
        rejectIfNull(map, "map");
        name = rejectIfBlank(name, "key").trim();

        final var value = map.remove(name);
        if (value != null) {
            return value.toString();
        } else {
            return "";
        }
    }

    /**
     * 获取指定键的整数形式的值。
     *
     * @param map
     *         包含需要获取的值的字典。
     * @param name
     *         参数的名字，会自动去掉首尾空格。
     * @return 参数的Integer形式的值，如果无法解析为整数则返回{@code null}。
     * @throws NullPointerException
     *         如果参数 {@code map} 是 {@code null} 。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public static Integer getInteger(
            Map<String, ?> map,
            String name
    ) {
        try {
            return Integer.parseInt(getString(map, name));
        } catch (NumberFormatException ignored) {
            return null;
        }
    }

    /**
     * 获取指定键的整数形式的值。
     *
     * @param map
     *         包含需要获取的值的字典。
     * @param name
     *         参数的名字，会自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 参数的Integer形式的值，如果无法解析为整数则返回默认值。
     * @throws NullPointerException
     *         如果参数 {@code map} 是 {@code null} 。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public static int getInteger(
            Map<String, ?> map,
            String name,
            int defaultValue
    ) {
        final var result = getInteger(map, name);
        return result != null ? result : defaultValue;
    }

    public static Integer takeInteger(
            Map<String, ?> map,
            String name
    ) {
        try {
            return Integer.parseInt(takeString(map, name));
        } catch (NumberFormatException ignored) {
            return null;
        }
    }

    /**
     * getInteger。
     *
     * @param map
     *         包含需要获取的参数的字典。
     * @param name
     *         参数的名字，会自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 参数的Integer形式的值，如果无法解析为整数则返回默认值。
     * @throws NullPointerException
     *         如果参数 {@code map} 是 {@code null} 。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public static int takeInteger(
            Map<String, ?> map,
            String name,
            int defaultValue
    ) {
        final var result = takeInteger(map, name);
        return result != null ? result : defaultValue;
    }

    public static Long getLong(
            Map<String, ?> map,
            String name
    ) {
        try {
            return Long.parseLong(getString(map, name));
        } catch (NumberFormatException ignored) {
            return null;
        }
    }


    /**
     * 获取Long形式的参数的值。
     *
     * @param map
     *         包含需要获取的参数的字典。
     * @param name
     *         参数的名字，会自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 参数的Long形式的值，如果无法解析则返回默认值。
     * @throws NullPointerException
     *         如果参数 {@code map} 是 {@code null} 。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public static long getLong(
            Map<String, ?> map,
            String name,
            long defaultValue
    ) {
        final var result = getLong(map, name);
        return result != null ? result : defaultValue;
    }

    public static Double getDouble(
            Map<String, ?> map,
            String name
    ) {
        try {
            return Double.parseDouble(getString(map, name));
        } catch (NumberFormatException ignored) {
            return null;
        }
    }

    public static double getDouble(
            Map<String, ?> map,
            String name,
            double defaultValue
    ) {
        final var result = getDouble(map, name);
        return result != null ? result : defaultValue;
    }

    public static LocalDate getLocalDate(
            Map<String, ?> map,
            String name
    ) {
        final var s = getString(map, name);
        var result = parseLocalDate(s, null);
        if (result == null) {
            final var l = parseLong(s, null);
            if (l != null) {
                result = new Date(l).toInstant().query(TemporalQueries.localDate());
            }
        }

        return result;
    }

    /**
     * 获取LocalDate形式的参数的值。
     * <p>依次按照{@literal yyyy/MM/dd}，{@literal yyyy-MM-dd}，{@literal yyyyMMdd}的格式尝试解析日期。</p>
     *
     * @param map
     *         包含需要获取的参数的字典。
     * @param name
     *         参数的名字，会自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 参数的Date形式的值，如果无法被解析为LocalDate类型则返回默认值。
     * @throws NullPointerException
     *         如果参数 {@code map} 是 {@code null} 。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     * @see java.text.DateFormat#parse(String)
     */
    public static LocalDate getLocalDate(
            Map<String, ?> map,
            String name,
            LocalDate defaultValue
    ) {
        final var result = getLocalDate(map, name);
        return result != null ? result : defaultValue;
    }

    public static Date getDate(
            Map<String, ?> map,
            String name
    ) {
        final var s = getString(map, name);
        var result = parseDate(s, null);
        if (result == null) {
            final var l = parseLong(s, null);
            if (l != null) {
                result = new Date(l * 1000);
            }
        }

        return result;
    }

    /**
     * 获取Date形式的参数的值。
     * <p>依次按照{@literal yyyy/MM/dd}，{@literal yyyy-MM-dd}，{@literal yyyyMMdd}的格式尝试解析日期。</p>
     *
     * @param map
     *         包含需要获取的参数的字典。
     * @param name
     *         参数的名字，会自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 参数的Date形式的值，如果无法被解析为Date类型则返回默认值。
     * @throws NullPointerException
     *         如果参数 {@code map} 是 {@code null} 。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     * @see java.text.DateFormat#parse(String)
     */
    public static Date getDate(
            Map<String, ?> map,
            String name,
            Date defaultValue
    ) {
        final var result = getDate(map, name);
        return result != null ? result : defaultValue;
    }

    public static Boolean getBoolean(
            Map<String, ?> map,
            String name
    ) {
        final String s = getString(map, name);
        if ("true".equals(s) || "yes".equals(s) || "on".equals(s) || "t".equals(s) || "y".equals(s) || "1".equals(s)) {
            return true;
        } else if ("false".equals(s) || "no".equals(s) || "off".equals(s) || "f".equals(s) || "n".equals(s) || "0".equals(s)) {
            return false;
        } else {
            return null;
        }
    }

    /**
     * 获取Boolean形式的参数的值。
     *
     * @param map
     *         包含需要获取的参数的字典。
     * @param name
     *         参数的名字，会自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 参数的Boolean形式的值，如果无法解析则返回默认值。
     * @throws NullPointerException
     *         如果参数 {@code map} 是 {@code null} 。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public static boolean getBoolean(
            Map<String, ?> map,
            String name,
            boolean defaultValue
    ) {
        final var result = getBoolean(map, name);
        return result != null ? result : defaultValue;
    }

    public static BigDecimal getDecimal(
            Map<String, ?> map,
            String name
    ) {
        try {
            return new BigDecimal(getString(map, name));
        } catch (NumberFormatException ignored) {
            return null;
        }
    }

    /**
     * 获取BigDecimal形式的参数的值。
     *
     * @param map
     *         包含需要获取的参数的字典。
     * @param name
     *         参数的名字，会自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 参数的BigDecimal形式的值，如果无法解析为十进制则返回默认值。
     * @throws NullPointerException
     *         如果参数 {@code map} 是 {@code null} 。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public static BigDecimal getDecimal(
            Map<String, ?> map,
            String name,
            BigDecimal defaultValue
    ) {
        final var result = getDecimal(map, name);
        return result != null ? result : defaultValue;
    }

    public static <E extends Enum<E> & IntegerEnum> E getIntegerEnum(
            Map<String, ?> map,
            String name,
            Class<E> clazz,
            E defaultValue
    ) {
        try {
            return toIntegerEnum(getInteger(map, name), clazz);
        } catch (IllegalArgumentException ex) {
            return defaultValue;
        }
    }

    public static <E extends Enum<E> & StringEnum> E getStringEnum(
            Map<String, ?> map,
            String name,
            Class<E> clazz,
            E defaultValue
    ) {
        try {
            return toStringEnum(getString(map, name), clazz);
        } catch (IllegalArgumentException ex) {
            return defaultValue;
        }
    }

    public static int[] getIntegerArray(
            Map<String, ?> map,
            String name,
            int defaultValue
    ) {
        final var s = getString(map, name);

        return stream(split(s, ","))
                .map(String::trim)
                .mapToInt(s_ -> parseInteger(s_, defaultValue))
                .toArray();
    }

    public static String[] getStringArray(
            Map<String, ?> map,
            String name
    ) {
        final var s = getString(map, name);

        return stream(split(s, ","))
                .map(String::trim)
                .toArray(String[]::new);
    }

    /**
     * 从Map中获取指定键的值，并转化为对象列表。
     *
     * @param map
     *         原始Map。
     * @param name
     *         键，自动去掉首尾空格。
     * @return 键对应的值，并转化为对象列表。
     * @throws IllegalArgumentException
     *         如果参数{@code map}是{@code null}，如果参数{@code key}是{@code null}或者只包含空白字符。
     * @throws ClassCastException
     *         如果键对应的值无法被转化为列表。如果键对应的值能够转化为列表，但是列表的项不是Map。
     */
    public static List<Map<String, Object>> getListOfMap(
            Map<String, ?> map,
            String name
    ) {
        rejectIfNull(map, "map");
        name = rejectIfBlank(name, "key").trim();

        final var value = map.get(name);
        if (value == null) {
            return Collections.emptyList();
        } else if (value instanceof final Collection<?> c) {
            final var result = new ArrayList<Map<String, Object>>(((Collection<?>) value).size());
            for (final var item : c) {
                if (item instanceof Map) {
                    result.add(((Map<?, ?>) item)
                                       .entrySet().stream()
                                       .filter(entry -> entry.getKey() != null && !Objects.equals(entry.getKey(), ""))
                                       .filter(entry -> entry.getValue() != null)
                                       .collect(toMap(
                                               entry -> entry.getKey().toString(),
                                               Map.Entry::getValue
                                       )));
                } else {
                    throw new ClassCastException("Cannot cast " + item + " to Map");
                }
            }
            return result;
        } else {
            throw new ClassCastException("Cannot cast " + value + " to Collection");
        }
    }

    public static <E extends Enum<E> & IntegerEnum> Set<E> getIntegerEnumSet(
            Map<String, ?> map,
            String name,
            Class<E> clazz,
            E defaultValue
    ) {
        final var s = getString(map, name);

        return stream(split(s, ","))
                .map(String::trim)
                .map(s_ -> {
                    try {
                        return toIntegerEnum(parseInteger(s_, 0), clazz);
                    } catch (IllegalArgumentException ex) {
                        return defaultValue;
                    }
                })
                .filter(Objects::nonNull)
                .collect(toSet());
    }


    public static <E extends Enum<E> & StringEnum> Set<E> getStringEnumSet(
            Map<String, ?> map,
            String name,
            Class<E> clazz,
            E defaultValue
    ) {
        final var s = getString(map, name);

        return stream(split(s, ","))
                .map(String::trim)
                .map(s_ -> {
                    try {
                        return toStringEnum(s_, clazz);
                    } catch (IllegalArgumentException ex) {
                        return defaultValue;
                    }
                })
                .filter(Objects::nonNull)
                .collect(toSet());
    }

    @SafeVarargs
    public static <K> boolean equalsByKeys(
            Map<K, ?> m1,
            Map<K, ?> m2,
            K... keys
    ) {
        if (keys == null || keys.length == 0) {
            return true;
        }

        for (final K k : keys) {
            if (k != null) {
                final var v1 = m1 != null ? m1.get(k) : null;
                final var v2 = m2 != null ? m2.get(k) : null;

                if (!Objects.equals(v1, v2)) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * 将Map转化为键类型为 {@link String} 的Map。
     * <p>如果参数 {@code src} 是 {@code null}，那么返回空Map。</p>
     *
     * @param src
     *         原Map。
     * @return 转换后的Map。
     */
    public static Map<String, Object> asStringMap(
            Map<?, ?> src
    ) {
        final var result = new LinkedHashMap<String, Object>();

        if (src != null && !src.isEmpty()) {
            for (final var entry : src.entrySet()) {
                final var sk = entry.getKey() != null ? entry.getKey().toString() : "";
                result.put(sk, entry.getValue());
            }
        }

        return result;
    }

    public static <K, V> Map<K, V> singleKeyMap(
            K key,
            V value
    ) {
        final var result = new HashMap<K, V>();

        if (key != null && value != null) {
            result.put(key, value);
        }

        return unmodifiableMap(result);
    }
}
